
package com.trydofor.id.doer.int64;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.LoggerFactory;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.body.int64.Int64QueryBody;
import com.trydofor.id.body.int64.Int64ReplyBody;
import com.trydofor.id.byta.Head;
import com.trydofor.id.byta.Packet;
import com.trydofor.id.core.A9Maker;
import com.trydofor.id.core.A9Makers;
import com.trydofor.id.doer.PacketDoer;
import com.trydofor.id.logs.Eventable;
import com.trydofor.id.logs.Eventer;
import com.trydofor.id.logs.LogLevel;

/**
 * @author : Shi Rongjiu (www.trydofor.com)
 */
public class Int64Doer implements PacketDoer, Eventable {
    
    /**
     * the events
     */
    public enum Event implements Eventer.Event {
        /**
         * added a new maker
         */
        MAKER_ADDEDNEW,
        /**
         * remove a maker
         */
        MAKER_REMOVED,
        /**
         * replaced a old maker with a new one
         */
        MAKER_REPLACED,
        /**
         * can NOT find a maker for the worker and type
         */
        MAKER_NOTFOUND,
        /**
         * the maker makes null
         */
        MAKER_MADENULL
    }
    
    private final Eventer                                eventer;
    private final Map<String, A9Maker<? extends Number>> a9maker;
    private transient long                               waitnil;
    
    /**
     * constructor
     */
    public Int64Doer() {
        a9maker = new ConcurrentHashMap<String, A9Maker<? extends Number>>();
        eventer = new Eventer(LoggerFactory.getLogger(Int64Doer.class));
        // set default event log level
        eventer.register(Event.MAKER_ADDEDNEW, LogLevel.INFO);
        eventer.register(Event.MAKER_REMOVED, LogLevel.INFO);
        eventer.register(Event.MAKER_REPLACED, LogLevel.WARN);
        eventer.register(Event.MAKER_NOTFOUND, LogLevel.WARN);
        eventer.register(Event.MAKER_MADENULL, LogLevel.WARN);
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean accept(Packet request) {
        if (request.getBody() instanceof Int64QueryBody) {
            Int64QueryBody fetchBody = (Int64QueryBody) request.getBody();
            String mk = fetchBody.getMaker();
            if (mk == null) return false;
            if (fetchBody.getCount() <= 0) return false;
            if (!a9maker.containsKey(mk)) return false;
            return true;
        }
        return false;
    }
    
    /**
     * {@inheritDoc}
     */
    public Packet response(Packet request) {
        Head fetchHead = request.getHead();
        Int64QueryBody fetchBody = (Int64QueryBody) request.getBody(); // throw
        String mk = fetchBody.getMaker();
        int count = fetchBody.getCount();
        int worker = fetchHead.getAuthCode();
        Head replyHead = new Head(worker, fetchHead.getDealCode());
        Int64ReplyBody replyBody = new Int64ReplyBody(mk, count);
        Packet reply = new Packet(replyHead, replyBody);
        
        A9Maker<? extends Number> maker = a9maker.get(mk);
        if (maker == null) {
            eventer.log(Event.MAKER_NOTFOUND, "maker Not found, type={},head={}", mk, fetchHead);
        }
        else {
            int i = 0;
            long nms = -1;
            while (i < count) {
                Number dc = maker.make();
                if (dc == null) {
                    if (nms < 0) {
                        nms = System.currentTimeMillis();
                    }
                    else if (System.currentTimeMillis() - nms > waitnil) {
                        eventer.log(Event.MAKER_MADENULL, "made null after {}, type={},head={}", i, mk,
                                fetchHead);
                        break; //  break while
                    }
                }
                else {
                    nms = -1;
                    replyBody.append(dc.longValue());
                    i++;
                }
            }
        }
        
        return reply;
    }
    
    /**
     * {@inheritDoc}
     */
    public Eventer getEventer() {
        return eventer;
    }
    
    /**
     * register the maker.
     * 
     * @param name the maker name
     * @param maker the maker
     * @return old maker if existed.
     * @throws NullPointerException if name or maker is null
     */
    public A9Maker<? extends Number> register(String name, A9Maker<? extends Number> maker) {
        Exceptions.checkNullPointer(name);
        Exceptions.checkNullPointer(maker);
        
        A9Maker<? extends Number> old = a9maker.put(name, maker);
        
        if (old == null) {
            eventer.log(Event.MAKER_ADDEDNEW, "added a new maker, name={},new={}", name, maker);
        }
        else {
            eventer.log(Event.MAKER_REPLACED, "replaced the maker, name={},old={},new={}", name, old, maker);
        }
        return old;
    }
    
    /**
     * unregister the maker.
     * 
     * @param name the maker name
     * @return the old maker
     * @throws NullPointerException if name is null
     * @see Map#remove(Object)
     */
    public A9Maker<? extends Number> unregister(String name) {
        Exceptions.checkNullPointer(name);
        A9Maker<? extends Number> old = a9maker.remove(name);
        eventer.log(Event.MAKER_REMOVED, "unregister a maker, name={},maker={}", name, old);
        return old;
    }
    
    /**
     * list of maker
     * 
     * @return the info
     */
    public List<String> listMaker() {
        return new ArrayList<String>(a9maker.keySet());
    }
    
    /**
     * @return the wait milliseconds for null
     */
    public long getWaitNull() {
        return waitnil;
    }
    
    /**
     * @param waitnil milliseconds for null
     */
    public void setWaitNull(long waitnil) {
        this.waitnil = waitnil;
        
        a9maker.put("1", A9Makers.defaultA9icMaker(1));
        a9maker.put("1", A9Makers.defaultA9idSecondMaker(1));
    }
    
}
